package cn.sciento.transfer.infra.data.check.mapping;

import cn.sciento.core.exception.CommonException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MappedFile {
  private static final Logger LOGGER = LoggerFactory.getLogger(MappedFile.class);
  
  private String filePath;
  
  private Long offset;
  
  private int size;
  
  private RandomAccessFile fd;
  
  private FileChannel fdChannel;
  
  private MappedByteBuffer mappedByteBuffer;
  
  public MappedFile(String filePath, Long offset, int size) {
    this.filePath = filePath;
    this.offset = offset;
    this.size = size;
    init();
  }
  
  public Long appendContent(ByteBuffer byteBuffer) {
    try {
      return writeBuffer(byteBuffer);
    } catch (IOException e) {
      LOGGER.error("write file error", e);
      return Long.valueOf(-1L);
    } finally {
      try {
        this.fdChannel.close();
      } catch (IOException iOException) {}
    } 
  }
  
  public Long appendContentWithOutClose(ByteBuffer byteBuffer) {
    try {
      return writeBuffer(byteBuffer);
    } catch (IOException e) {
      LOGGER.error("write file error", e);
      return Long.valueOf(-1L);
    } 
  }
  
  public ByteBuffer readByteBuffer(Long offset, int size) {
    ByteBuffer byteBuffer = ByteBuffer.allocate(size);
    try {
      readBuffer(offset, byteBuffer);
    } catch (IOException e) {
      throw new CommonException(e);
    } finally {
      try {
        if (this.fdChannel != null) {
          this.fdChannel.close();
        }
      } catch (IOException iOException) {}
    } 
    byteBuffer.flip();
    return byteBuffer;
  }
  
  public ByteBuffer readByteBufferWithOutClose(Long offset, int size) {
    ByteBuffer byteBuffer = ByteBuffer.allocate(size);
    try {
      readBuffer(offset, byteBuffer);
    } catch (IOException e) {
      throw new CommonException(e);
    } 
    byteBuffer.flip();
    return byteBuffer;
  }
  
  public void close() {
    try {
      this.fdChannel.close();
    } catch (IOException iOException) {}
  }
  
  public void deleteFile() {
    if (this.fdChannel.isOpen())
      close(); 
    File file = new File(this.filePath);
    if (file.exists())
      file.delete(); 
  }
  
  public MappedByteBuffer mappingFileInMemory() throws IOException {
    if (this.size <= 0)
      this.size = (int)this.fd.length(); 
    if (this.offset.longValue() > this.fd.length())
      return null; 
    if (this.offset.longValue() + this.size > this.fd.length())
      this.size = (int)(this.fd.length() - this.offset.longValue()); 
    this.mappedByteBuffer = this.fdChannel.map(FileChannel.MapMode.READ_ONLY, this.offset.longValue(), this.size);
    return this.mappedByteBuffer;
  }
  
  public void closeMemoryFile() {
    AccessController.doPrivileged(new PrivilegedAction() {
          @Override
          public Object run() {
            try {
              Method cleaner = MappedFile.this.mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
              if (cleaner != null) {
                cleaner.setAccessible(true);
                // todo 这里有问题
//                cleaner.invoke(MappedFile.this.mappedByteBuffer).clean();

              } 
            } catch (Exception exception) {}
            return null;
          }
        });
  }
  
  private Long writeBuffer(ByteBuffer byteBuffer) throws IOException {
    try {
      long offset = this.fdChannel.size();
      this.fd.seek(offset);
      byteBuffer.flip();
      while (byteBuffer.hasRemaining())
        this.fdChannel.write(byteBuffer); 
      return Long.valueOf(offset);
    } catch (IOException e) {
      throw e;
    } 
  }
  
  private void readBuffer(Long offset, ByteBuffer byteBuffer) throws IOException {
    try {
      long skipBytes = this.offset.longValue() + offset.longValue();
      if (this.fd.length() < skipBytes)
        return; 
      this.fd.seek(skipBytes);
      this.fdChannel.read(byteBuffer);
    } catch (IOException e) {
      throw e;
    } 
  }
  
  private void init() {
    File file = new File(this.filePath);
    makeParentDirIsOk(file);
    try {
      if (!file.exists()) {
        boolean result = file.createNewFile();
        if (LOGGER.isDebugEnabled())
          LOGGER.debug("create file[{}] scs", Boolean.valueOf(result)); 
      } else if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("file exists,skip create file[{}],but this is not normal", this.filePath);
      } 
      this.fd = new RandomAccessFile(file, "rw");
      this.fdChannel = this.fd.getChannel();
    } catch (IOException e) {
      throw new CommonException(e.getMessage());
    } 
  }
  
  private void makeParentDirIsOk(File file) {
    if (file.getParent() != null) {
      File parent = new File(file.getParent());
      if (parent.isDirectory() && !parent.exists()) {
        boolean result = parent.mkdirs();
        if (LOGGER.isDebugEnabled())
          LOGGER.debug("create parent dir[{}]", Boolean.valueOf(result)); 
      } 
    } 
  }
}
